''' Sets up datapacket context

Datapacket context is set of resources to support later datapacket handling.
Context currently contains logical datasource (lds) resources. Logical
datasource is tenant specific datasource.

data_packet{'context':{'resources':{<lds_name>:{'url':...,
                                                'schema':...}}}}

'''
from __future__ import absolute_import

import os
from sqlalchemy import create_engine, Table

from scopt.util.misc import dir_name, accept
from scopt.util import message as msg_util
from scopt.mpu.context.tenant.handlers import message as context_message
from scopt.mpu.context.devel.handlers import message as devel_message
from scopt.mpu.context.devel.handlers import mpu_get
from scopt.mpu.context.devel.handlers import mpu_list
from scopt.mpu.context import model
from scopt.util import sqlalchemy_util

I18N = {'context': os.path.join(dir_name(__file__), 'i18n')}

_CONTEXT_HANDLERS = {}
_CONTEXT_HANDLERS.update(context_message.registry())
_CONTEXT_HANDLERS.update(devel_message.registry())

mpu_get.set_context_handlers(_CONTEXT_HANDLERS)
mpu_list.set_context_handlers(_CONTEXT_HANDLERS)


class Context():
    def __init__(self, config):
        self.__data_dir = config.get('APP', 'DATA_DIR')
        self.__context_url = self.__adapt_url(config.get('CONTEXT', 'url'),
                                              self.__data_dir)
        self.lds_bds_url = config.get('lds/BDS', 'url')
        self.lds_bds_schema = config.get('lds/BDS', 'schema')
        self.__metadata = model.get()
        self.__context_init()
        self.__connection = None
        self.__engine = None
        self.__tx = None

    def __context_init(self):
        engine = create_engine(self.__context_url, echo=False)
        self.__metadata.create_all(engine)

    @accept(status=['AFP'])
    def __call__(self, data_packet):
        # TODO: Prior to any call connection must be validated
        # TODO: Sounds silly but think of closing connection after every
        # request.
        if self.__connection is None:
            self.__engine = create_engine(self.__context_url, echo=False)
            self.__connection = self.__engine.connect()
        handler_id = msg_util.get_handler_id(data_packet)
        if handler_id in _CONTEXT_HANDLERS:
            data_packet = _CONTEXT_HANDLERS[handler_id](data_packet, self)
        else:
            data_packet = self.request_context_init(data_packet)
        if self.__is_init_requested(data_packet):
            context = self.__init_data_packet_context(data_packet)
            data_packet['context'] = context
            data_packet['demo'] = self.__is_demo(data_packet)
        return data_packet

    def request_context_init(self, data_packet):
        data_packet['context_init_request'] = True
        return data_packet

    def __is_init_requested(self, data_packet):
        if 'context_init_request' in data_packet:
            del data_packet['context_init_request']
            return True
        else:
            return False

    def __init_data_packet_context(self, data_packet):
        return {'resources': self.__resolve_datasources(data_packet)}

    def __resolve_datasources(self, data_packet):
        tenant_id = msg_util.get_tenant_id(data_packet)
        # TODO: So far just one lds exists. We need to add generic lds support
        physical_ds, schema = \
            self.__find_physical_ds_by_tenant_id(tenant_id, 'lds/BDS')
        tenant_url = self.__map_physical_ds_to_url(tenant_id, physical_ds)
        return {'lds/BDS': {'url': tenant_url,
                            'schema': schema}}

    def __find_physical_ds_by_tenant_id(self, tenant_id, logical_ds_name):
        result = self.query('tenants_params',
                            columns=['tenants_params.param_value'],
                            where=[['tenants_params.tenant_fk', tenant_id],
                                   ['tenants_params.param_name',
                                    logical_ds_name],
                                   ['tenants_params.scope', 'private']])
        return result[0]['param_value'].split('.')

    def __map_physical_ds_to_url(self, tenant_id, physical_ds):
        result = self.query('ds',
                            columns=['ds.url'],
                            where=[['ds.id', physical_ds]])
        url_template = result[0]['url']
        adapted_url = self.__adapt_url(url_template, self.__data_dir)
        adapted_url = adapted_url.replace('{TENANT_ID}', tenant_id)
        return adapted_url

    def __is_demo(self, data_packet):
        tenant_id = msg_util.get_tenant_id(data_packet)
        result = self.query('tenants_params',
                            columns=['tenants_params.param_value'],
                            where=[['tenants_params.tenant_fk', tenant_id],
                                   ['tenants_params.param_name', 'demo'],
                                   ['tenants_params.scope', 'public']])
        if len(result) == 0:
            demo = False
        else:
            demo = (result[0]['param_value'].strip() == 'True')
        return demo

    def __adapt_url(self, url, data_dir):
        url = url.replace('{DATA_DIR}', data_dir)
        return url.replace('\\', '/') if url.startswith('sqlite:') else url

    def tx_begin(self):
        self.__tx = self.__connection.begin()

    def tx_commit(self):
        self.__tx.commit()
        self.__tx = None

    def tx_rollback(self):
        self.__tx.rollback()
        self.__tx = None

    def table(self, table_name):
        return Table(table_name, self.__metadata)

    def execute(self, command):
        return self.__connection.execute(command)

    def query(self, tables, columns=None, where=None, order=None,
              mapping=None, mapper=None):
        query = sqlalchemy_util.query(self.__metadata, tables, columns, where,
                                      order, mapping)
        result = self.execute(query)
        rows = result.fetchall()
        result.close()
        if mapper is not None:
            rows = mapper(rows)
        return rows

    def update(self, table_name, values, where=None, mapping=None):
        update = sqlalchemy_util.update(self.__metadata, table_name, values,
                                        where, mapping)
        self.execute(update)

    def insert(self, table_name, values, mapping=None):
        insert = sqlalchemy_util.insert(self.__metadata, table_name,
                                        values, mapping)
        pk = self.execute(insert).inserted_primary_key

        if len(pk) == 1:
            return pk[0]
